<?php
Zend_Loader::loadClass('Lead_Event_Observable_Abstract');
Zend_Loader::loadClass('Lead_EDO_Handler_Interface');
Zend_Loader::loadClass('Lead_Tool_File_Searcher');


/**
 * Abstract EDO handler.
 * 
 * @category Lead
 * @package EDO
 *
 */
abstract class Lead_EDO_Handler_Abstract extends Lead_Event_Observable_Abstract implements Lead_EDO_Handler_Interface 
{
    
    protected static $_quotedFieldTypes = array(
        'String', 'Char', 'Text', 'Datetime', 'Date'
    );
        
    
	/**
	 * Lead_Db object
	 *
	 * @var Lead_Db_Adapter_Interface
	 */
	public $db;



	/**
	 * FileSearcher object
	 *
	 * @var FileSearcher
	 */
	private $_searcher;

	/**
	 * LDB mode (with or without transactions)
	 *
	 * @var integer
	 */
	protected $_mode;

	private $_types_classnames = array();
	/**
	 * Classnames to types conversion array
	 *
	 * @var array
	 */
	private $_classnames_types = array();

	/**
	 * Dynamic EDO types array
	 *
	 * @var array
	 */
	private $_dynamic_types = array();


	/**
	 * Default events common for all created edos
	 *
	 * @var array
	 */
	private $defaultEvents = array();



	private $_attribs = array();


	/**
	 * Is the handler initialized?
	 *
	 * @var boolean
	 */
	private $_initialized = false;

	
	
	private $_cache = false;
	
	
	/**
	 * Zend_Cache frontend
	 *
	 * @var Zend_Cache_Core
	 */
	private $_cacheFrontend;


	/**
	 * Zend_Cache backend
	 *
	 * @var Zend_Cache_Backend_Interface
	 */
	private $_cacheBackend = null;

	private $_typedata = array();

	private $_hash;
	
	
	private $_addedTables = array();
	
	
	private $_identityMap;
	
	private $_unitOfWork;
	
	protected $_persister;
	
	
	

	/**
	 * Initializes the EDO handler.
	 *
	 * @param LDB_base $ldb_object LDB object reference
	 */
	public function __construct(Lead_Db_Adapter_Interface $db)
	{
		
	    // spl_autoload_register(array($this, '_requireType'));
	    
	    /*
	    // We decorate the Zend_Db object with our own Lead_Db adapter.
		// Decoration didn't work, got fucking segfault.
		$class = get_class($db);
        $class = str_ireplace('Zend', 'Lead', $class);
        Zend_Loader::loadClass($class);
		*/

		$this->db = $db;
				
	    $this->_searcher = new Lead_Tool_File_Searcher();

		$this->_initialize();

	}
	
	/**
	 * Fetches EDO types and classnames and assigns the conversion arrays.
	 *
	 */
	private function _initialize()
	{
        if(!$this->_initialized) {
    
            $phrase = "SELECT * FROM edo_types";
            $result = $this->db->fetchAll($phrase);

            foreach($result as $row) {
            	$this->_types_classnames[$row['type_id']] = $row['classname'];
            	$this->_classnames_types[$row['classname']] = $row['type_id'];
            	if($row['dynamic'])
            		$this->_dynamic_types[] = $row['classname'];
            }
            $this->_initialized = true;
        }
	}
		

	
	/**
	 * Adds a new EDO classpath.
	 *
	 * @param string $path
	 */
	public function addPath($path) {
	    $this->_searcher->addPath($path);
	}
	
	
	
	/**
	 * Returns an EDO of a specified type
	 *
	 * @param string $className EDO classname
	 * @return Lead_EDO_Object_Abstract
	 */
	public function get($type)
	{
		if(in_array($type, $this->getDynamicTypes())) {
		    throw new Lead_EDO_Exception('Dynamicity not yet re-implemented');
		    // require_once EDO::getRoot() . '/Core/Objects/class.EDO_object_dynamic.php';
			// $obj = new EDO_object_dynamic($this, $type);
		} else {
		    $this->_requireType($type);
			$obj = new $type($this);
		}
     	return $obj;
	}
	
	
	/**
	 * Requires an EDO type
	 *
	 * @param string $edo_type EDO type (name or typeid)
	 */
	protected function _requireType($type)
	{
	    if(intval($type))
			$type = $this->convert($type);
					
		$path = $this->_searcher->find("{$type}/{$type}.php");

		if(!$path)
			throw new Lead_EDO_Exception("EDO '{$type}' not found", 666);

		require_once $path;

	}
	
	
	/**
	 * Converts EDO's typeid to name or vice versa.
	 *
	 * @todo Rethink this. This looks weird so it's maybe bad.
	 * @param mixed $edo_type
	 * @return string|integer
	 */
	public function convert($edo_type)
	{
	    if(!$edo_type)
		  throw new Lead_EDO_Exception('Null edo type in convert function');

		if(intval($edo_type))
		{
            if(!key_exists($edo_type, $this->_types_classnames))
                throw new Lead_EDO_Exception('EDO typeid not found in array');
                
            return $this->_types_classnames[$edo_type]; 
		}
		else
		{
		    if(!key_exists($edo_type, $this->_classnames_types))
            {
                // is this automagicity good here?
	                // Afterthought: yes, it helps but if all are not inserted thru this => buggy.
                $newTypeId = $this->db->nextId('edo_typeid');
                $this->db->insert('edo_types', array('type_id' => $newTypeId, 'classname' => $edo_type, 'dynamic' => 0));
                return $newTypeId;
            }
            
            return $this->_classnames_types[$edo_type];

		}

    }
	
	
	public function setCache($cacheOpt) {
	    $this->_cache = (bool) $cacheOpt;
	}
	
	
	public function getCache() {
	    return $this->_cache;
	}
	
	
	
	
	
	public function setCacheBackend(Zend_Cache_Backend_Interface $backend)
	{
	    $this->_cacheBackend = $backend;
	    $frontendOptions = array('automaticSerialization' => true);
	    Zend_Loader::loadClass('Zend_Cache');
        Zend_Loader::loadClass('Zend_Cache_Core');
        $this->_cacheFrontend = new Zend_Cache_Core($frontendOptions);
        $this->_cacheFrontend->setBackend($this->_cacheBackend);
	}


	public function getCacheBackEnd()
	{
	    return $this->_cacheBackend;
	}


	public function getCacheFrontEnd()
	{
	    return $this->_cacheFrontend;
	}

	
	
	
	public function getType(Lead_EDO_Object_Abstract $obj)
	{
	    if(!key_exists($obj->getClass(), $this->_typedata)) {
            
	        Zend_Loader::loadClass('Lead_EDO_Type');
	        if(!$this->getCache()) {
	            
               $type = new Lead_EDO_Type($obj);
               // $type->init($obj);
	            
	        } else {

		        $cache = $this->getCacheFrontEnd();
	            if(!$type = $cache->load("type_{$obj->getClass()}"))
	            {
	
	                $type = new Lead_EDO_Type();
	                $type->init($obj);
	                $cache->save($type, "type_{$obj->getClass()}");
	
	            }
	            
	        }
	        
	        
            $this->_typedata[$obj->getClass()] = $type;
	    }

		return $this->_typedata[$obj->getClass()];

	}
	
	
	
	/**
	 * Defines type, initializes, fetches and returns an EDO object.
	 *
	 * @param integer $id id
	 * @return Lead_EDO_Object_Abstract
	 */
	public function fetch($id, $type = null)
	{
	    $this->notifyAll('onFetch', $id);
	    
	    try {
	        return $this->_persister->fetch($id, $type);    
	    } catch(Exception $e) {

	        throw $e;
	    }
	    
	    
	}
	
	
	public function insert(Lead_EDO_Object_Abstract $obj, $clear = true)
	{
	    if($obj->isFetched()) {
	        throw new Lead_EDO_Exception("Cannot insert an already existing object with id: {$obj->id}");
	    }
	    
	    $this->notifyAll('onInsert', $obj);
	    
    	// $obj->notifyAll('onInsertSuccess');
	    // $obj->notifyAll('onInsertFailed');
	    	    
	    return $this->_persister->insert($obj, $clear);
	}


	public function save(Lead_EDO_Object_Abstract $obj)
	{
		// $obj->notifyAll('onSaveSuccess');
		// $obj->notifyAll('onSaveFailed');
        // $obj->notifyAll('onSave');
	    $this->notifyAll('onSave', $obj);
	    return $this->_persister->save($obj);
	}


	public function delete(Lead_EDO_Object_Abstract $obj)
	{
	    // $this->notifyAll('onDelete');
	    
	    
	    $this->notifyAll('onDelete', $obj);
	    return $this->_persister->delete($obj);
	}
	
	
	
	
	/**
	 * Returns finder for EDO type.
	 *
	 * @param mixed $type EDO object or EDO type
	 * @return Lead_EDO_Finder
	 */
	public function getFinder($type)
	{
	    if(!$type instanceof Lead_EDO_Object_Abstract)
	       $type = $this->get($type);

	    Zend_Loader::loadClass('Lead_EDO_Finder');
	    return new Lead_EDO_Finder($type);
	}
	
	
	
	/**
	 * Returns an EDO collection
	 *
	 * @param mixed $objectType EDO object typeid or name
	 * @param array $keys an array of ids
	 * @param string $collectionType Collection type (basic, paged)
	 * @return EDO_collection
	 */
	public function collection($objectType, $keys = array(), $collectionType = 'basic')
	{
		Zend_Loader::loadClass('Lead_EDO_Collection');
	    $coll = Lead_EDO_Collection::factory($collectionType, $this);
		$coll->setObject($objectType);
		$coll->setItems($keys);
		return $coll;
	}
		
	
	/**
	 * Returns an array of all types.
	 * Array format $arr[(int)typeid] = array(class=>string,dynamic=>boolean)
	 *
	 * @return Array
	 */
	public function getTypes()
	{
		$resArr = array();
		foreach($this->_classnames_types as $classname => $typeid)
		{
			$dynamic = in_array($classname, $this->_dynamic_types);
			$resArr[$typeid] = array('class' => $classname, 'dynamic' => $dynamic);
		}
		return $resArr;
	}
	
		
	
	public function getAttribute($key, $defaultValue = null)
	{
	    return (key_exists($key, $this->_attribs)) ? $this->_attribs[$key] : $defaultValue;
	}


	public function setAttribute($key, $value)
	{
	    $this->_attribs[$key] = $value;
	}

	public function getAttributes()
	{
	   return $this->_attribs;
	}
	
	
	/**
	 * Returns an array of dynamic types
	 *
	 * @return Array
	 */
	public function getDynamicTypes()
	{
		return $this->_dynamic_types;
	}
	
		
	
	
	/* AFTER THIS, TO BE CHECKED */
	
	
	/* AFTER THIS, NU TO BE DOCCED AND TAGGED */

	
	public function valueIsQuoted($field)
	{
	    if(!$field instanceof Lead_EDO_Datatype_Field_Value)
	        throw new Lead_EDO_Exception('Not an instance of field.');
	    return (in_array($field->getType(), self::$_quotedFieldTypes)) ? true : false;
	}
	
	
	public function getDB()
	{
	    return $this->db;
	}
	
	
	
	public function addTable($tableName) {
	    $this->_addedTables[] = $tableName;
	}
	
	
	public function getAddedTables()
	{
	    return $this->_addedTables;
	}
	
}
?>